﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Pay.Core;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;

namespace Pay.Service {
    /// <summary>
    /// StackExchange.Redis管理者
    /// 注意：这个客户端没有连接池的概念，而是有了多路复用技术
    /// </summary>
    public class RedisManager {
        /// <summary>
        /// 锁对象
        /// </summary>
        private static object _locker = new object();
        private ConnectionMultiplexer connectionMultiplexer;
        private int db;
        private static RedisManager instance;

        /// <summary>
        /// 得到StackExchange.Redis单例对象
        /// </summary>
        public static RedisManager Instance {
            get {
                if (instance == null) {
                    lock (_locker) {
                        if (instance != null)
                            return instance;

                        instance = new RedisManager();
                        return instance;
                    }
                }

                return instance;
            }
        }

        public RedisManager() {
            var conn = ConfigurationManager.ConnectionStrings["CacheRedis"];
            var connarr = conn.ConnectionString.Split('@');
            db = int.Parse(conn.ProviderName);
            var pwd = connarr[0];
            var address = connarr[1];
            if (!string.IsNullOrEmpty(pwd)) address += ",password=" + pwd;
            address += ",defaultDatabase=" + db;
            var options = ConfigurationOptions.Parse(address);
            options.ConnectTimeout = 1000;//连接操作超时（ms）
            options.KeepAlive = 180;//发送消息以帮助保持套接字活动的时间（秒）（默认为60秒）
            options.SyncTimeout = 2000;//时间（ms）允许进行同步操作
            options.ConnectRetry = 3;//重试连接的次数
            connectionMultiplexer = ConnectionMultiplexer.Connect(options);
        }

        private const int _expirySeconds = 60;
        private JsonSerializerSettings jsonConfig = new JsonSerializerSettings() {
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            NullValueHandling = NullValueHandling.Ignore
        };

        public bool Set(string key, object value, int seconds = 0) {
            if (key == null) throw new ArgumentNullException("key");
            if (value != null) {
                if (seconds == 0) seconds = _expirySeconds;
                var dt = DateTime.Now;
                var timeSpan = dt.AddSeconds(seconds) - dt;
                var data = JsonConvert.SerializeObject(value, jsonConfig);
                try {
                    var database = connectionMultiplexer.GetDatabase();
                    return database.StringSet(key, data, timeSpan);
                } catch { }
            }
            return false;
        }

        private bool Set(IDatabase db, string key, object value, int seconds = 0) {
            if (key == null) throw new ArgumentNullException("key");
            if (value != null) {
                if (seconds == 0) seconds = _expirySeconds;
                var dt = DateTime.Now;
                var timeSpan = dt.AddSeconds(seconds) - dt;
                var data = JsonConvert.SerializeObject(value, jsonConfig);
                try {
                    return db.StringSet(key, data, timeSpan);
                } catch { }
            }
            return false;
        }

        public T GetOrCreate<T>(string cacheKey, Func<T> create, int seconds = int.MaxValue) {
            var tags = cacheKey.Split(':');
            var tag = tags[0];
            var key = tags[1];
            RedisValue cacheValue = RedisValue.Null;
            IDatabase database = null;
            try {
                database = connectionMultiplexer.GetDatabase();
                cacheValue = database.StringGet(tag);
            } catch {
            }
            if (!cacheValue.IsNull) {
                var joject = JsonConvert.DeserializeObject<JObject>(cacheValue, jsonConfig);
                JToken jtoken = null;
                if (joject.TryGetValue(key, out jtoken)) {
                    return jtoken.ToObject<T>();
                } else {
                    var result = create();
                    if (result == null) return result;
                    joject.Add(key, JToken.FromObject(result));
                    Set(database, tag, joject, seconds);
                    return result;
                }
            } else {
                var result = create();
                if (result == null) return result;
                var joject = new JObject();
                joject.Add(key, JToken.FromObject(result));
                Set(database, tag, joject, seconds);
                return result;
            }
        }

        public T Get<T>(string key) {
            if (key == null) throw new ArgumentNullException("key");

            var value = default(T);
            try {
                var database = connectionMultiplexer.GetDatabase();
                var cacheValue = database.StringGet(key);
                if (!cacheValue.IsNull) {
                    value = JsonConvert.DeserializeObject<T>(cacheValue, jsonConfig);
                    return value;
                }
            } catch { }
            return value;
        }

        public bool Remove(string key) {
            var database = connectionMultiplexer.GetDatabase();
            return database.KeyDelete(key);
        }

        public void Remove(IEnumerable<string> tags) {
            var count = tags.Count();
            if (count > 0) {
                RedisKey[] keys = new RedisKey[count];
                tags.ForEach((tag, i) => {
                    keys[i] = tag;
                });
                try {
                    var database = connectionMultiplexer.GetDatabase();
                    database.KeyDelete(keys);
                } catch { }
            }
        }

        public bool RemoveAll() {
            var server = connectionMultiplexer.GetServer(connectionMultiplexer.GetEndPoints()[0]);
            server.FlushDatabase(database: db);
            return true;
        }
    }
}